
#include <set>
#include <JSL.hpp>

#include "../../debug.hpp"
#include "../../math.hpp"

#include "Animation.hpp"

namespace zen {

	namespace video {

		namespace model {

			Animation::Animation() {
				keyCount = 0;
				keys = NULL;
			}

			Animation::Animation(const Animation &other) {
				keyCount = 0;
				keys = NULL;
				*this = other;
			}

			Animation::~Animation() {
				delete[] keys;
			}

			Animation& Animation::operator = (const Animation &other) {
				framer = other.framer;
				if (keys != nullptr)
					delete[] keys;
				keyCount = other.keyCount;
				keys = new AnimKey[keyCount];
				for (glm::uint i = 0; i < keyCount; i++)
					keys[i] = other.keys[i];
				return *this;
			}

			glm::mat4 Animation::computeKey(glm::uint position, glm::uint keyCount, AnimKey* keys, glm::uint startKey, glm::uint endKey) {
				if (keys == NULL)
					return glm::mat4();
				if (position <= keys[0].time)
					return keys[0].key;
				glm::uint lastIndex = keyCount - 1;
				if (position >= keys[lastIndex].time)
					return keys[lastIndex].key;
				glm::uint kpos = searchKeyByPosition(position, keyCount, keys, startKey, endKey);
				if (kpos >= lastIndex) {
					return keys[lastIndex].key;
				} else {
					glm::u32 time1 = keys[kpos].time;
					glm::u32 time2 = keys[kpos + 1].time;
					return glm::interpolate(keys[kpos].key, keys[kpos + 1].key, (position - time1) / (float)(time2 - time1));
				}
			}

			glm::uint Animation::searchKeyByPosition(glm::uint position, glm::uint keyCount, AnimKey* keys, glm::uint start, glm::uint end) {
				// TODO: implement something faster than binary search
				while (start != end) {
					int middle = (start + end) / 2;
					if (position < keys[middle].time)
						end = middle;
					else
						start = middle;
				}
				if (start > 0 && position < keys[start].time)
					start--;
				return start;
			}

			void Animation::refreshStartEndKeyIndexes(AnimationFramer &framer) const {
				if (framer.getStart() < keys[framer.getStartKey()].time || (framer.getStartKey() < keyCount - 1 && framer.getStart() >= keys[framer.getStartKey() + 1].time))
					framer.setStartKey(searchKeyByPosition(framer.getStart(), keyCount, keys, 0, keyCount - 1));
				if (framer.getEnd() < keys[framer.getEndKey()].time || (framer.getEndKey() < keyCount - 1 && framer.getEnd() >= keys[framer.getEndKey() + 1].time))
					framer.setEndKey(searchKeyByPosition(framer.getEnd(), keyCount, keys, 0, keyCount - 1));
			}

			bool Animation::loadResource(jsl::Node* node) {
				zen_call("Animation.loadResource(jsl::Node*)");
				if (node == NULL) {
					zen_log("Error: Invalid resource, must not be null.");
					return false;
				}

				delete[] keys;
				keys = NULL;
				keyCount = 0;

				try {
					jsl::Node *tmp, *tmp2;
					framer.setType(AnimTypeNone);
					framer.setSpeed(1.0);
					framer.setStart(node->at("start")->asInt());
					framer.setEnd(node->at("end")->asInt());

					// position keys
					tmp = node->at("position");
					glm::uint positionKeyCount = (glm::uint)tmp->size();
					AnimKey* positionKeys = NULL;
					if (positionKeyCount > 0) {
						positionKeys = new AnimKey[positionKeyCount];
						for (glm::uint i = 0; i < positionKeyCount; i++) {
							tmp2 = tmp->at(i);
							positionKeys[i].time = (glm::u32)tmp2->at("time")->asInt();
							tmp2 = tmp2->at("vec3");
							positionKeys[i].key = glm::translate(tmp2->at(0)->asFloat(), tmp2->at(1)->asFloat(), tmp2->at(2)->asFloat());
						}
					}

					// rotation keys
					tmp = node->at("rotation");
					glm::uint rotationKeyCount = (glm::uint)tmp->size();
					AnimKey* rotationKeys = NULL;
					if (rotationKeyCount > 0) {
						rotationKeys = new AnimKey[rotationKeyCount];
						for (glm::uint i = 0; i < rotationKeyCount; i++) {
							tmp2 = tmp->at(i);
							rotationKeys[i].time = tmp2->at("time")->asInt();
							if (tmp2->at("quat") != NULL) {
								tmp2 = tmp2->at("quat");
								rotationKeys[i].key = glm::mat4_cast(glm::quat(tmp2->at(0)->asFloat(), tmp2->at(1)->asFloat(), tmp2->at(2)->asFloat(), tmp2->at(3)->asFloat()));
							}  else if (tmp2->at("mat3x3") != NULL) {
								tmp2 = tmp2->at("mat3x3");
								rotationKeys[i].key = glm::mat4(
									glm::vec4(tmp2->at(0)->asFloat(), tmp2->at(1)->asFloat(), tmp2->at(2)->asFloat(), 0.0),
									glm::vec4(tmp2->at(3)->asFloat(), tmp2->at(4)->asFloat(), tmp2->at(5)->asFloat(), 0.0),
									glm::vec4(tmp2->at(6)->asFloat(), tmp2->at(7)->asFloat(), tmp2->at(8)->asFloat(), 0.0),
									glm::vec4(0.0, 0.0, 0.0, 1.0));
							} else if (tmp2->at("euler") != NULL) {
								tmp2 = tmp2->at("euler");
								rotationKeys[i].key = glm::matrixFromEulerAngles(tmp2->at(0)->asFloat(), tmp2->at(1)->asFloat(), tmp2->at(2)->asFloat());
							} else {
								delete[] rotationKeys;
								rotationKeys = NULL;
								rotationKeyCount = 0;
								zen_log("Warning: Invalid rotation keys.");
								break;
							}
						}
					}

					// scale keys
					tmp = node->at("scale");
					glm::uint scaleKeyCount = (glm::uint)tmp->size();
					AnimKey* scaleKeys = NULL;
					jsl::JSLarraySize scales = tmp->size();
					if (scaleKeyCount > 0) {
						scaleKeys = new AnimKey[scaleKeyCount];
						for (glm::uint i = 0; i < scaleKeyCount; i++) {
							tmp2 = tmp->at(i);
							scaleKeys[i].time = tmp2->at("time")->asInt();
							tmp2 = tmp2->at("vec3");
							scaleKeys[i].key = glm::scale(tmp2->at(0)->asFloat(), tmp2->at(1)->asFloat(), tmp2->at(2)->asFloat());
						}
					}

					// merge keys
					std::set<glm::uint> animKeysTmp;
					for (glm::uint i = 0; i < positionKeyCount; i++)
						animKeysTmp.insert(positionKeys[i].time);
					for (glm::uint i = 0; i < rotationKeyCount; i++)
						animKeysTmp.insert(rotationKeys[i].time);
					for (glm::uint i = 0; i < scaleKeyCount; i++)
						animKeysTmp.insert(scaleKeys[i].time);
					std::vector<glm::uint> animKeys(animKeysTmp.begin(), animKeysTmp.end());
					std::sort(animKeys.begin(), animKeys.end());

					keyCount = (glm::uint)animKeys.size();
					if (keyCount > 0) {
						keys = new AnimKey[keyCount];
						std::vector<glm::uint>::iterator iter = animKeys.begin();
						for (glm::uint i = 0; i < keyCount; i++, iter++) {
							keys[i].time = *iter;
							glm::uint position = keys[i].time;
							keys[i].key = computeKey(position, positionKeyCount, positionKeys, 0, positionKeyCount - 1) * computeKey(position, rotationKeyCount, rotationKeys, 0, rotationKeyCount - 1) * computeKey(position, scaleKeyCount, scaleKeys, 0, scaleKeyCount - 1);
						}
					}

					delete[] positionKeys;
					delete[] rotationKeys;
					delete[] scaleKeys;

					refreshStartEndKeyIndexes(framer);

				} catch (const std::exception &e) {
					zen_log("Error: " + std::string(e.what()));
					return false;
				}
				return true;
			}


		} // namespace model

	} // namespace video

} // namespace zen
